/*
   Copyright [2013] [Entrib Technologies]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

//---------------------------------------------------------------------------------
// Mongoslate grammar
//
// The purpose of this grammar is to allow SQL like querying mechanism for MongoDB.
// 
// Author: Atul Dambalkar (atul@entrib.com)
//---------------------------------------------------------------------------------
parser grammar MongoslateParser;

options {

    // Default language but name it anyway
    language  = Java;

    // output as AST
    output = AST;

    // Use a superclass to implement all helper methods, instance variables and overrides
    // of ANTLR default methods, such as error handling.
    //
    superClass = AbstractTParser;

    // Use the vocabulary generated by the accompanying lexer. Maven knows how to work out the relationship
    // between the lexer and parser and will build the lexer before the parser. It will also rebuild the
    // parser if the lexer changes.
    tokenVocab = MongoslateLexer;
}

// Some imaginary tokens for tree rewrites
tokens {
	UNARY_NOT;
	UNARY_MINUS;
	UNARY_EXISTS;
	VALUE_LIST;
	TAG_LIST;
	COLLECTION;
	LOGICAL_EXPRESSION;
	TAG_CLAUSE;
	LIMIT_EXPRESSION;
	ORDER_BY;
	ORDER_BY_FIELD;
	GROUP_BY;
	AGGREGATION_FUNCTION;
	DATA_OPERATOR;
	GEOCOORDINATE;
	DISTANCE;
}

// What package should the generated source exist in?
@header {
	package com.entrib.mongoslate;
}

prog
    :
    (
        select_stmt
//        | insert_stmt
//        | update_stmt
//        | remove_stmt
    )
    EOF
    ;

select_stmt
    :
    SELECT
    (distinct)? 
    tag_list_clause
    from_clause
    WHERE
    logical_expression_clause
    (group_by_clause)?
    (having_clause)?
    (order_by_clause)?
    (limit_clause)?

        -> ^(SELECT (distinct)?
                tag_list_clause
                from_clause
                logical_expression_clause
                (group_by_clause)?
                (having_clause)?
                (order_by_clause)?
                (limit_clause)?
             )
    ;

distinct
    :
    DISTINCT
    ;

group_by_clause
    :
    GROUP BY (IDENTIFIER) (COMMA IDENTIFIER)* -> ^(GROUP_BY IDENTIFIER+)
    ;

having_clause
    : HAVING logical_expression_clause -> ^(HAVING logical_expression_clause)
    ;

order_by_clause
    :
    ORDER BY (order_by_expression) (COMMA order_by_expression)* -> ^(ORDER_BY order_by_expression+)
    ;

order_by_expression
    : IDENTIFIER order -> ^(ORDER_BY_FIELD IDENTIFIER order)
    ;

order
    :
    ASC
    |
    DESC
    ;

limit_clause
    :
    LIMIT INT (COMMA INT)? -> ^(LIMIT_EXPRESSION INT (INT)?)
    ;
    
tag_list_clause
    : tag_clause (COMMA tag_clause)* -> ^(TAG_LIST tag_clause+)
    ;

tag_clause
    :
    name=IDENTIFIER -> ^(TAG_CLAUSE $name)
//    name=IDENTIFIER (AS alias=IDENTIFIER)? -> ^(TAG_CLAUSE $name ($alias)?)
    |
    aggregation_function LP name=IDENTIFIER RP (AS alias=IDENTIFIER)? -> ^(AGGREGATION_FUNCTION aggregation_function $name ($alias)?)
    |
    data_operator LP name=IDENTIFIER RP (AS alias=IDENTIFIER)? -> ^(DATA_OPERATOR data_operator  $name ($alias)?)
    ;

aggregation_function
    :
    SUM
    |
    COUNT
    |
    MAX
    |
    MIN
    |
    AVG
    ;

data_operator
    :
    YEAR
    |
    MONTH
    |
    WEEK
    |
    DAY_OF_MONTH
    |
    DAY_OF_WEEK
    |
    DAY_OF_YEAR
    |
    HOUR
    |
    MINUTE
    |
    SECOND
    |
    TO_UPPER
    |
    TO_LOWER
   ;

from_clause
    : FROM collection -> ^(COLLECTION collection)
    ;

collection
    : IDENTIFIER
    ;

logical_expression_clause
    : logical_expression -> ^(LOGICAL_EXPRESSION logical_expression)
    ;
    
logical_expression
     : relational_expression (
             (
                 AND^
                 |
                 NOR^
                 |
                 OR^
             )
       relational_expression
     )*  
     ;

data_value_list
     : LB data_value (COMMA data_value)* RB -> ^(VALUE_LIST data_value+)
     ;

primary_value
     : data_value
     | IDENTIFIER
     ;

data_value
    : INT
    | FLOAT
    | STRING
    | timestamp
    | location
    | shape
    ;

shape
    : box
    | polygon
    | circle
    ;

timestamp
    :
    TIMESTAMP^ LP! QUOTE! FORMATTED_DATE QUOTE! RP!
    ;

location
    :
    LOCATION LP geocoordinate ((COMMA distance)?) RP -> ^(LOCATION geocoordinate (distance)?)
    ;

box
    :
    BOX LP bottomLeft=geocoordinate COMMA topRight=geocoordinate RP -> ^(BOX $bottomLeft $topRight)
    ;

polygon
    :
    POLYGON LP geocoordinate ((COMMA geocoordinate)+) RP -> ^(POLYGON (geocoordinate)+)
    ;

circle
    :
    CIRCLE LP geocoordinate COMMA radius RP -> ^(CIRCLE geocoordinate radius)
    ;

geocoordinate
    :
    LSB x=number COMMA y=number RSB -> ^(GEOCOORDINATE $x $y)
    ;

distance
    : number
    ;

radius
    : number
    ;
    
number
    :
    INT
    |FLOAT
    ;

primary_expression
     : primary_value
     | data_value_list
     | LP! logical_expression RP!
     ;

negation_expression
     : NOT negation_expression ->  ^(UNARY_NOT negation_expression)
     | primary_expression
     ;

exists_expression
     : EXISTS LP tag RP ->  ^(UNARY_EXISTS tag)
     ;

tag
    : IDENTIFIER
    ;

relational_expression
     : arith_expression (
         (
             EQ^
             |
             NE^
             |
             GT^
             |
             GTE^
             |
             LT^
             |
             LTE^
             |
             IN^
             |
             NIN^
             |
             CONTAINS^
             |
             STARTS^
             |
             ENDS^
             |
             ALL^
             |
             NEAR^
             |
             WITHIN^
         )
         arith_expression
     )*
     ;

arith_expression
     : multiplying_expression (

         ( PLUS^ | MINUS^ )

       multiplying_expression)*
     ;

sign_expression
     : MINUS sign_expression ->  ^(UNARY_MINUS sign_expression)
     | negation_expression
     | exists_expression
     ;

multiplying_expression
     : sign_expression (

         ( MULTI^ | DIV^ | MOD^ )

       sign_expression)*
     ;
